const Fs = require("fs");
const {createWriteStream} = Fs;
const Util = require("util");
const bytenode = require("bytenode");
import fetch from "node-fetch";
import { assert } from "console";
const Progress = require("./Progress.jsc");
const sleep = require("./Sleep.jsc");

async function progess(list: Array<string>, fn: () => Promise<void>): Promise<void> {
	let nan = new Progress(list);
	nan.start();
	try {
		await fn();
	} catch(err) {
		nan.error();
		throw err;
	} finally {
		nan.end();
	}
	return;
}



async function download(videoList: Array<string>, aid: number | string): Promise<Array<Buffer>> {
	let uri: string = Util.format("https://api.bilibili.com/x/web-interface/view?aid=%s", aid);
	let headers = {
		"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.0",
		"Accept": "*/*",
		"Accept-Language": "en-US,en;q=0.5",
		"Accept-Encoding": "gzip, deflate, br",
		"Range": "bytes=0-",
		"Referer": uri,
		"Origin": "https://www.bilibili.com",
		"Connection": "keep-alive"
	};
	let result = new Array<Buffer>();
	for (var item of videoList) {
		try {
			var res = await fetch(item,{"headers": headers})/*Superagent.get(item).set(headers).timeout({
				response: 1500,
				deadline: undefined
			});*/
			result.push(await res.buffer());
		} catch (err) {
			throw err;
		}
	};
	return Promise.resolve(result);
}

async function getInfo(aid: number | string | String, p: undefined | number | Array<number> = undefined): Promise<object> {
	let result = new Object();
	let headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.0" };
	let uri = Util.format("https://api.bilibili.com/x/web-interface/view?aid=%s", aid);
	try {
		let res = await fetch(uri,{"headers": headers});
		let json = await res.json();
		result["title"] = json["data"]["title"];
		if (p === undefined) {
			result["videoList"] = json["data"]["pages"];
		} else if (typeof p == "number") {
			result["videoList"] = [json["data"]["pages"][p - 1]];
		} else {
			var results = new Array<object>();
			for (var i of p) {
				results.push(json["data"]["pages"][i - 1]);
			}
			result["videoList"] = results;
		}
	} catch (err) {
		throw err;
	}
	return Promise.resolve(result);
}


async function getLinks(aid: number | string | String, cid: number | string | String, quality: number | string | String, sessdata: string | String): Promise<Array<string>> {
	let result = new Array<string>();
	let headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.0", Cookie: Util.format("SESSDATA=%s", sessdata), Host: "api.bilibili.com" };
	try {
		let res = await fetch(Util.format("https://api.bilibili.com/x/player/playurl?cid=%s&avid=%s&qn=%s", cid, aid, quality),{"headers": headers});
		let json = await res.json();
		for (var item of json["data"]["durl"]) {
			result.push(item["url"]);
		}
	} catch (err) {
		throw err;
	}
	return Promise.resolve(result);
}


async function main(args : Array<string>|Array<String>) : Promise<number> {
	switch(args[0]) {
		case "video" : {
			let aid = args[1];
			let bvid : string|String;
			try {
				if (aid.match(/^BV/) != null) {
					bvid = aid;
					aid = Util.format("%s",(bv => {
						const pos = [11, 10, 3, 8, 4, 6];
						const base = 'fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF';
						const table = {};
						for (var i = 0; i < base.length; ++i) {
							table[base[i]] = i;
						}
						let r = 0;
						for (var i = 0; i < pos.length; ++i) {
							r += table[bv[pos[i]]] * 58 ** i;
						}
						return (r - 8728348608) ^ 177451812;
					})(bvid));
				} else {
					bvid = Util.format("%s",(aid => {
						const pos = [11, 10, 3, 8, 4, 6];
						const base = 'fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF';
						let x = (Number(aid) ^ 177451812) + 8728348608;
						let r=["B","V","1"," "," ","4"," ","1"," ","7"," "," "];
						for (var i=0;i<6;++i) {
							r[pos[i]]=base[Math.floor(x/(58**i))%58];
						}
						return r.join("");
					})(aid));
				}
				let qu = args[2];
				let info = await getInfo(aid);
				//console.log(info);
				console.log(Util.format("Title: %s",info["title"]));
				if (info["videoList"].length > 1) {
					console.log(Util.format("Aid: %s  Bvid: %s",aid,bvid));
					console.log(Util.format("Length: %s",info["videoList"].length));
					console.log("Pages:");
					for (var i=0;i<info["videoList"].length;++i) {
						console.log(Util.format("-- %s  cid:",info["videoList"][i]["part"],info["videoList"][i]["cid"]))
					}
				} else {
					console.log(Util.format("Aid: %s  Bvid: %s  Cid: %s",aid,bvid,info["videoList"][0]["cid"]));
				}
				let p: number[];
				if (info["videoList"].length == 1) {
					p = [0];
				} else {
					if (args[3] != undefined) {
						var tmp:Array<string> = args[3].split(/,/);
						var lisp = new Array<number>();
						for (var i=0;i<tmp.length;++i) {
							if (tmp[i].match(/-/) != null) {
								lisp.push(i);
							}
						}
						//console.log(tmp);
						//console.log(lisp);
						if (lisp.length > 0) {
							for (var i=0;i<lisp.length;++i) {
								var tem = lisp[i];
								var double = tmp[tem].split(/-/).map(item => {
									return Number(item);
								});
								//console.log(double);
								assert(double[1] > double[0]);
								var fal = double[1] - double[0];
								var start = tmp.slice(0,lisp[i]);
								var end = tmp.slice(lisp[i] + 1);
								var add = new Array<number>();
								for (var j=double[0];j<=double[1];++j) {
									add.push(j);
								}
								tmp = start.concat(add.map(item => {return String(item)})).concat(end);
								if (i==lisp.length-1) break;
								for (var j=i+1;j<lisp.length;++j) {
									lisp[j] += fal;
								}
							}
						}
						//console.log(tmp);
						p = tmp.map(item => {
							return Number(item) -1;
						});
					} else {
						p = new Array<number>();
						for (var i=0;i<info["videoList"].length;++i) {
							p.push(i);
						}
					}
				}
				let sessdata = "2574c4f7%2C1600753539%2C1f099*31";
				let uri: string = Util.format("https://api.bilibili.com/x/web-interface/view?aid=%s", aid);
				let headers = {
					"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.0",
					"Accept": "*/*",
					"Accept-Language": "en-US,en;q=0.5",
					"Accept-Encoding": "gzip, deflate, br",
					"Range": "bytes=0-",
					"Referer": uri,
					"Origin": "https://www.bilibili.com",
					"Connection": "keep-alive"
				};
				let list = ["/", "-", "\\", "|"];
				if (p.length > 1) console.log(Util.format("To Download: %s",p.map(item => item+1).join(",")));
				for (var pos in p) {
					var cid = info["videoList"][pos]["cid"];
					let links = await getLinks(aid,cid,qu,sessdata);
					await progess(list, async () => {
						for (var i = 0; i < links.length; ++i) {
							let file = await fetch(links[i],{"headers": headers});
							let dest = await Fs.createWriteStream(p.length == 1 ? (links.length == 1 ? Util.format("%s.flv", info["title"].replace(/\//g, ";")) : Util.format("%s #%s.flv", info["title"].replace(/\//g, ";"),i)) : (links.length == 1 ? Util.format("%s --%s.flv", info["title"].replace(/\//g, ";"),info["videoList"][p[pos]]["part"]) : Util.format("%s --%s #%s.flv", info["title"].replace(/\//g, ";"),info["videoList"][p[pos]]["part"],i)));
							var tmp = file.body.pipe(dest);
							let fun = async (t: { [x: string]: any; }) => {
								for (;;) {
									if (t["closed"]) break;
									await sleep(100);
								}
								return;
							}
							await fun(tmp);
							dest.end();
						}
						return;
					});
				}
				return Promise.resolve(0);
			} catch (err) {
				console.log(err);
				return Promise.resolve(127);
			}
			//return Promise.resolve(1);
		}
		case "bangumi" : {
			let num = args[1];
			let url : string;
			if (num.match(/^ep(.+)/) || num.match(/^ss(.+)/)) {
				//https://www.bilibili.com/bangumi/play/ep#{varible}
				let origin = num;
				num = num.match(/^ep(.+)/) != null ? num.match(/^ep(.+)/)[1] : num.match(/^ss(.+)/)[1];
				url = origin.match(/^ep(.+)/) != null ? Util.format("https://www.bilibili.com/bangumi/play/ep%s",num) : Util.format("https://www.bilibili.com/bangumi/play/ss%s",num);
			} else {
				console.log("Warning: You must give a right ep-number or ss-number");
				return Promise.resolve(127);
			}
			let re = /<script>window.__INITIAL_STATE__=(.*?);.+<\/script>/;
			let headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.0" };
			let http = await fetch(url,{headers: headers});
			let html = await http.text();
			//console.log(html);
			let json : Object;
			if (html.match(re) != null) {
				let results = html.match(re);
				//console.log(result);
				json = JSON.parse(results[1]);
				//console.log(json);
			} else {
				return Promise.resolve(127);
			}
			let result = {"title": json["mediaInfo"]["title"]};
			let videoList = new Array<Object>();
			for (var i=0;i<json["epList"].length;++i) {
				var list = new Object();
				list["aid"] = json["epList"][i]["aid"];
				list["cid"] = json["epList"][i]["cid"];
				list["title"] = json["epList"][i]["title"];
				list["longTitle"] = json["epList"][i]["longTitle"];
				videoList.push(list);
			}
			result["videoList"] = videoList;
			//console.log(result);
			console.log(Util.format("title: %s  aid: %s  bvid: %s",result["title"],result["videoList"][0]["aid"],(aid => {
				const pos = [11, 10, 3, 8, 4, 6];
				const base = 'fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF';
				let x = (Number(aid) ^ 177451812) + 8728348608;
				let r=["B","V","1"," "," ","4"," ","1"," ","7"," "," "];
				for (var i=0;i<6;++i) {
					r[pos[i]]=base[Math.floor(x/(58**i))%58];
				}
				return r.join("");
			})(result["videoList"][0]["aid"])));
			console.log(Util.format("Length: %s",result["videoList"].length));
			console.log("Pages:");
			for (var item of result["videoList"]) {
				console.log(Util.format("--%s: %s  cid: %s",item["title"],item["longTitle"],item["cid"]));
			}
			return Promise.resolve(0);
		}
		case "m3u8": {
			return Promise.resolve(1);
			try {
				let file = Fs.readFileSync(args[1]).toString();
				let links = file.split(/\n/).filter(item => item.match(/^#/) == null);
				console.log(links);
			} catch(e) {
				console.log(e);
				return Promise.resolve(127);
			}
			return Promise.resolve(0);
		}
	}
	console.log("You must give a common");
	return Promise.resolve(1);
}

/*
async function main(args: Array<string>): Promise<number> {
	let aid = args[0];
	let qu = args[1];
	//let p = args[2];
	let info = await getInfo(aid);
	let sessdata = "2574c4f7%2C1600753539%2C1f099*31";
	var length = info["videoList"].length;
	for (var pos = 0; pos < length; ++pos) {
		let links = await getLinks(aid, info["videoList"][pos]["cid"], qu, sessdata);
		let list = ["/", "-", "\\", "|"];
		await progess(list, async () => {
			//let file = await download(links, aid);
			
			let uri: string = Util.format("https://api.bilibili.com/x/web-interface/view?aid=%s", aid);
			let headers = {
				"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.0",
				*/
				//"Accept": "*/*",
			/*	"Accept-Language": "en-US,en;q=0.5",
				"Accept-Encoding": "gzip, deflate, br",
				"Range": "bytes=0-",
				"Referer": uri,
				"Origin": "https://www.bilibili.com",
				"Connection": "keep-alive"
			};
			for (var i = 0; i < links.length; ++i) {
				let file = await fetch(links[i],{"headers": headers});
				/*.then(async res => {
					const dest = Fs.createWriteStream(Util.format("%s --part %s\:%s #%s.flv", info["title"].replace(/\//g, ";"), pos + 1, info["videoList"][pos]["part"], i));
					await res.body.pipe(dest);
				})*/
				/*let dest = await Fs.createWriteStream(Util.format("%s --part %s\:%s #%s.flv", info["title"].replace(/\//g, ";"), pos + 1, info["videoList"][pos]["part"], i));
				var tmp = file.body.pipe(dest);
				
				//await dest.end();
				let fun = async (t) => {
					for (;;) {
						if (t["closed"]) {
							break;
						} else {
							await sleep(100);
						}
					}
					return;
				}
				await fun(tmp);
				dest.end();
				//Fs.writeFileSync(Util.format("%s --part %s\:%s #%s.flv", info["title"].replace(/\//g, ";"), pos + 1, info["videoList"][pos]["part"], i), file[i]);
				//delete file[i];
			}
			return;
		});
	}
	return Promise.resolve(0);
}
*/

/*
function _function(str : string) : void {
	let list = ["/","-","\\","|"];
	if (global["isRuning"]) {
		log("Downloading " + str);
		let next = (str == "|") ? "/" : list[list.indexOf(str)+1];
		setTimeout(_function,100,next);
	} else {
		log.clear();
		console.log("Downloading Down");
	}
}
*/


if ((global["byte"] !== undefined && global["byte"] == true) || (typeof require != "undefined" && require.main === module && module.parent == undefined)) {
	global["byte"] = false;
	global["biliwget"] = new Object();
	let args = process.argv.splice(2);
	main(args).then(item => {
		process.exit(item);
	})
}
//Fs.writeFileSync("test.flv", b[0])

module.exports = {
	download: download
};

//求f(x)=x3-2x2+x-4的减区间
//(1/3,1)